package edu.cs.usask.ca.silver.businesslogic.commands.generic;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import com.vensim.Vensim;

import edu.cs.usask.ca.silver.api.businesslogic.SilverServiceI;
import edu.cs.usask.ca.silver.api.persistence.StorageI;
import edu.cs.usask.ca.silver.businesslogic.ids.ScenarioID;
import edu.cs.usask.ca.silver.businesslogic.util.SilverAutoManagedObjectFactory;
import edu.cs.usask.ca.silver.definitions.SystemProperties;
import edu.cs.usask.ca.silver.exceptions.UnableToInitializeStorageException;
import edu.cs.usask.ca.silver.exceptions.VensimLogicErrorException;
import edu.cs.usask.ca.silver.persistence.datasource.EmbeddedDerbyStorage;
import edu.cs.usask.ca.silver.persistence.datasource.PgSQLStorage;
import edu.cs.usask.ca.silver.persistence.datasource.RemoteDerbyStorage;
import edu.cs.usask.ca.silver.util.parsers.fileparsers.AlpFileReader;

@SuppressWarnings("deprecation")
public class SilverService implements SilverServiceI {

	private static Vensim vensim;
	private static Logger logger = Logger.getLogger(SilverService.class);
	private static final SilverAutoManagedObjectFactory silverServiceFactory = new SilverAutoManagedObjectFactory(
			"edu.cs.usask.ca.silver.businesslogic.commands.generic.SilverService");

	public static SilverAutoManagedObjectFactory getObjectPoolFactory() {
		return silverServiceFactory;
	}

	/*
	 * (non-Javadoc)
	 * @see edu.cs.usask.ca.silver.api.businesslogic.SilverServiceI#getLocalDerbyStorage(java.lang.String, java.lang.String, java.lang.String, int, int)
	 */
	public StorageI getLocalDerbyStorage(String databaseLoc, String user,
			String pass, int poolSize, int connTimeOut) throws UnableToInitializeStorageException, Exception {
		return EmbeddedDerbyStorage.getLocalStorage(databaseLoc, user, pass, poolSize,
				connTimeOut);
	}

	/*
	 * (non-Javadoc)
	 * @see edu.cs.usask.ca.silver.api.businesslogic.SilverServiceI#getRemoteDerbyStorage(java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, int, int)
	 */
	public StorageI getRemoteDerbyStorage(String serverLoc,
			String databaseName, int port, String user, String pass,
			int poolSize, int connTimeOut) throws UnableToInitializeStorageException, Exception {
		return RemoteDerbyStorage.getremoteStorage(serverLoc, databaseName, port,
				user, pass, poolSize, connTimeOut);
	}
	
	@Deprecated
	public StorageI createPgSqlStorage(String serverLoc, String databaseName,
			int port, String user, String pass, int poolSize, int connTimeOut)
			throws Exception {
		return PgSQLStorage.getPgSQLStorage(serverLoc, databaseName, port,
				user, pass, poolSize, connTimeOut);
	}

	private static String setupAnylogicScenario(ScenarioID sId, StorageI storage)
			throws Exception {

		// 1. Load inputfiles for this scenario (jar of anylogic class files
		// that were created upon import - these are associated with the
		// original scenario)
		Document paramDOM = null;

		logger.info("----------");

		// -------------------------------------------------
		// ------ 1. Set up the parameter set XML (needed for parameter
		// overriding)
		int psInt = storage.getScenarioParameterSetId(sId.getUUIdVal());
		paramDOM = storage.getParameterSetParametersAsDom(psInt);

		// ///////////////
		// Generate the XML

		// set up a transformer
		TransformerFactory transfac = TransformerFactory.newInstance();
		Transformer trans = transfac.newTransformer();
		trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
		trans.setOutputProperty(OutputKeys.INDENT, "yes");

		// create string from xml tree
		StringWriter sw = new StringWriter();
		StreamResult result = new StreamResult(sw);
		DOMSource source = new DOMSource(paramDOM);
		trans.transform(source, result);
		String xmlString = sw.toString();

		// ///////////////
		// Save the String to file
		File temp = new File("tmp");
		if (!temp.mkdir())
			throw new Exception("can not create temporary folder");
		OutputStreamWriter oos = new OutputStreamWriter(new FileOutputStream(
				temp.getAbsolutePath() + File.separator + "ParamSet.xml"));
		oos.write(xmlString);
		oos.close();

		// -------------------------------------------------
		// ------ 2. Store the input files for this scenario to the temp
		// directory
		// Find the model version this scenario belongs to
		UUID mvInt = storage.getScenarioParentVersionId(sId.getUUIdVal());
		// Get the id of the original scenario for the model version of the
		// current scenario
		UUID origSInt = storage.getMvOriginalScenarioId(mvInt);
		// Retrieve all input file ids for the original scenario
		ArrayList<UUID> ifIdInts = storage.getScenarioInputFileIds(origSInt);

		// Retrieve all input files to temp directory
		// TODO - they're all being saved to "jarFileDest" because we're
		// assuming there's only a single
		// input file, namely the JAR file required to run a simulation.
		Iterator<UUID> ifIter = ifIdInts.iterator();
		while (ifIter.hasNext()) {
			// TODO - different files should be saved to different destinations!
			storage.getFFileToLocalStorage(ifIter.next(),
					new File(temp.getAbsolutePath() + File.separator
							+ "run.jar"));

		}

		// -------------------------------------------------
		// ------ 3. Store the alp file to disk temporarily to parse out package
		// name
		UUID sourceFId = storage.getMvSourceFileId(mvInt);
		// SilverService.getStorage().getFFileToLocalStorage(sourceFId,
		// temp.getAbsolutePath() + File.separator + "source.alp");
		File tempSource = new File(temp.getAbsolutePath() + File.separator
				+ "source.alp");
		storage.getFFileToLocalStorage(sourceFId, tempSource);

		AlpFileReader alpReader = new AlpFileReader();
		NodeList paramList = alpReader.buildPackageDOMFromAlp(tempSource)
				.getElementsByTagName("PackageName");

		return paramList.item(0).getTextContent();

	}

	/*
	 * (non-Javadoc)
	 * 
	 * TODO - set scenario Run end time when simulation is executed
	 * 
	 * @seeedu.usask.cs.silver.service.SilverServiceGeneral#
	 * runAnylogicScenarioScreenShot
	 * (edu.usask.cs.silver.service.components.ScenarioID)
	 */
	// public void runAnylogicScenarioScreenShot(ScenarioID sId, String
	// anylogicPath) throws Exception {
	//
	// String packageName = setupAnylogicScenario(sId);
	//
	// File temp = new File("tmp" + File.separator + "run.jar");
	// AlpSim mySim = AlpSim.alpSimWithScreenShotOSX(packageName, temp
	// .getAbsolutePath(), anylogicPath);
	// mySim.start();
	// }

	/*
	 * (non-Javadoc)
	 * 
	 * TODO - set scenario Run end time when simulation is executed
	 * 
	 * @seeedu.usask.cs.silvercore.service.SilverServiceGeneral#
	 * runAnylogicScenarioLogging
	 * (edu.usask.cs.silvercore.service.components.ScenarioID)
	 */
	// public void runAnylogicScenarioLogging(ScenarioID sId, String
	// anylogicPath) throws Exception {
	// String packageName = setupAnylogicScenario(sId);
	//
	// File temp = new File("tmp" + File.separator + "run.jar");
	// AlpSim mySim = AlpSim.alpSimWithLoggingOSX(packageName, temp
	// .getAbsolutePath(), anylogicPath);
	// mySim.start();
	//
	// }

	/*
	 * (non-Javadoc)
	 * 
	 * TODO - set scenario Run end time when simulation is executed
	 * 
	 * @see
	 * edu.usask.cs.silver.service.SilverServiceGeneral#runScenario(edu.usask
	 * .cs.silver.service.components.ScenarioID)
	 */
	// public void runAnylogicScenario(ScenarioID sId, String anylogicPath)
	// throws Exception {
	//
	// String packageName = setupAnylogicScenario(sId);
	// File temp = new File("tmp" + File.separator + "run.jar");
	// AlpSim mySim = AlpSim.alpSim(packageName, temp.getAbsolutePath(),
	// anylogicPath);
	// mySim.start();
	//
	// }

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * edu.usask.cs.silvercore.service.general.SilverServiceLocal#loadVenDll
	 * (java.lang.String)
	 */
	@Override
	public boolean loadVenDll(String libPath) throws Exception {
		if (System.getProperty("os.name").startsWith("Windows")) {
			logger.info("loaded VenDLL in SilverService");
			vensim = new Vensim(libPath);
			return true;
		}
		return false;
	}
	
	@Override
	public boolean vensimLibIsLoaded() {
		if (vensim == null)
			return false;
		
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * edu.usask.cs.silvercore.service.SilverServiceGeneral#runVensimScenario
	 * (edu.usask.cs.silvercore.service.components.ScenarioID)
	 */
	public void runVensimScenario(ScenarioID sId, String simName,
			StorageI storage) throws Exception {
		// 1. get source file associated with the parent ModelVersion
		String sourceFileLoc = RecreateSourceFileOnDiskAndReturnFullyQualifiedFilename(
				sId, storage);

		// 3. Load Vensim Model
		String venCommand = "SPECIAL>LOADMODEL|" + sourceFileLoc;

		if (Vensim.command(venCommand) == 1) {
			logger.info("Loading: " + sourceFileLoc + " succeeded");
		} else {
			logger.info("Loading: " + sourceFileLoc + " failed");
		}

		// 4. Read Parameters from Scenario;
		int psIdInt = storage.getScenarioParameterSetId(sId.getUUIdVal());
		ArrayList<Integer> list = storage.getParameterSetParameterIds(psIdInt);

		// 5. Compare to vpm parameter values with value from parameter set,
		// replace the
		// value in vpm if not identical
		String varName = null;
		float[] varVal = new float[2];
		float temp;
		for (Integer integer : list) {
			varName = storage.getParameterName(integer.intValue());
			Vensim.get_val(varName, varVal);
			temp = Float.valueOf(
					storage.getScenarioParameterValue(psIdInt,
							integer.intValue())).floatValue();

			// set the value if the value has been modified
			if (temp != varVal[0]) {
				venCommand = "SIMULATE>SETVAL|" + varName + " = " + temp;
				int result = Vensim.command(venCommand);
				if (result == 0) {
					// Log error;
					System.err.println("setting " + varName + " to " + temp
							+ "is failed");
				}
			}
		}

		// 6. Set the name for Simulation
		venCommand = "SIMULATE>RUNNAME|" + simName;
		int result = Vensim.command(venCommand);
		if (result == 0)
			logger.info("Output to " + simName + ".dat failed");
		else
			logger.info("Output to " + simName + ".dat succeeds");

		/*
		 * Deprecated -- should not use: GAMES override the values of constants
		 * with the default values // 7. initiate game result =
		 * Vensim.command("MENU>GAME"); if (result == 0)
		 * logger.info("Start game fails"); else
		 * logger.info("Start game succeeds");
		 */

		// 8. run the model;
		result = Vensim.command("MENU>RUN|O");
		if (result == 0)
			logger.info("MENU>RUN|O -- failed");
		else
			logger.info("run game succeeds");

		// 7. harvest trajectory file.

	}

	public String RecreateSourceFileOnDiskAndReturnFullyQualifiedFilename(
			ScenarioID scenarioId, StorageI storage) throws Exception {
		UUID fIdInt = storage.getMvSourceFileId(storage
				.getScenarioParentVersionId(scenarioId.getUUIdVal()));
		// 2. output vensim source file to temp location
		String fName = storage.getFName(fIdInt);
		String sourceFileLoc = SystemProperties.USER_WORK_DIR
		// + System.getProperty("file.separator") + "tmp"
				+ SystemProperties.PATH_SEPARATOR + fName;

		// String sourceFileLoc = "C:\\dll\\sim\\" + fName;

		storage.getFFileToLocalStorage(fIdInt, new File(sourceFileLoc));

		return sourceFileLoc;
	}

	// We'd ideally like to return a Map<ParameterID, Double> , but this was
	// causing a bizarre error
	// where the hash table wasn't working (see
	// CreateScenarioTableContentProvider)
	public Map<Integer, Double> ExtractValuesForParametersFromVDFTrajectoryFile(
			ScenarioID scenarioId, File addFile, StorageI storage)
			throws Exception, VensimLogicErrorException {
		/*
		 * String sourceFileLoc =
		 * RecreateSourceFileOnDiskAndReturnFullyQualifiedFilename(scenarioId);
		 * 
		 * String venCommand = "SPECIAL>LOADMODEL|" + sourceFileLoc;
		 * 
		 * if (Vensim.command(venCommand) == 1) { logger.info("Loading: "
		 * + sourceFileLoc + " succeeded"); } else {
		 * logger.info("Loading: " + sourceFileLoc + " failed"); }
		 */
		String strVDFPath = addFile.getPath();
		// clear all runs
		/*
		 * Vensim.command("SPECIAL>CLEARRUNS|"); // load a run
		 * 
		 * if (Vensim.command("SPECIAL>LOADRUN|" + strPath) == 1) {
		 * logger.info("Loading run: " + strPath + " succeeded"); } else
		 * { logger.info("Loading run: " + strPath + " failed"); }
		 */
		// TODO: probably should refactor this to use stateless beans? }

		int psIdInt = storage
				.getScenarioParameterSetId(scenarioId.getUUIdVal());
		ArrayList<Integer> list = storage.getParameterSetParameterIds(psIdInt);

		Hashtable<Integer, Double> map = new Hashtable<Integer, Double>();
		float arrayForOutputOfValues[] = new float[1];
		float arrayForOutputOfTimeValues[] = new float[1];
		int ctValuesToRetrieve = 1; // only retrieve one value (the initial
									// value)
		String strTimeAxis = "Time"; // indicates that we are interested in
										// retrieving values along the time axis

		for (int paramId : list) {
			String varName = storage.getParameterName(paramId);
			// Vensim.get_val will apparently only return constant values as
			// embedded in the MODEL

			// int returnCode = Vensim.get_val(varName, arrayForOutput);
			int returnCode = Vensim.get_data(strVDFPath, varName, strTimeAxis,
					arrayForOutputOfValues, arrayForOutputOfTimeValues,
					ctValuesToRetrieve);

			if (returnCode == 0) {
				// Log error;
				throw new VensimLogicErrorException("Reading " + varName + " from VDF "
						+ addFile.getName() + "has failed");
			}
			assert (returnCode != 0);
			double parameterValueInVDF = arrayForOutputOfValues[0];
			map.put(paramId, parameterValueInVDF);
		}

		return map;
	}

	@Override
	public void runAnylogicScenarioScreenShot(ScenarioID sId,
			String anylogicPath, StorageI storage) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public void runAnylogicScenarioLogging(ScenarioID sId, String anylogicPath,
			StorageI storage) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public void runAnylogicScenario(ScenarioID sId, String anylogicPath,
			StorageI storage) throws Exception {
		// TODO Auto-generated method stub

	}
}
